# -*- coding: utf-8 -*-

import random
from copy import copy

from damas import utils
from damas import saida

class Peca(str): pass

# constantes
C_NUMEROS = range(1, 9)
C_LETRAS = [chr(a) for a in range(ord('A'), ord('I'))]
PRETO = Peca('B')
BRANCO = Peca('W')
VAZIO = Peca(' ')

def evolui(p):
    # Tranforma uma peça em uma rainha
    p = copy(p)
    p.rainha = True
    return p

def rainha(p):
    # Testa se a peça é rainha
    return getattr(p, 'rainha', False)

def inicializa(tabuleiro, vazio=False):
    # Inicializa o tabuleiro, vom os brancos em cima e pretos embaixo, ou zerado
    for i in C_NUMEROS:
        d = bool(i % 2)
        for j in C_LETRAS:
            peca = (vazio and VAZIO or
                     ((0 < i < 4) and BRANCO or
                      (5 < i < 9) and PRETO or VAZIO))
            if d:
                tabuleiro[(i, j)] = peca
                d = False
            else:
                d = True
    return tabuleiro

def c_proximo(c, d):
    v = ord(c)
    return chr(v + d)

def posicao_valida(v, h):
    # verifica se a posição 'vaida
    if v < C_NUMEROS[0] or v > C_NUMEROS[-1]:
        return False
    if h < C_LETRAS[0] or h > C_LETRAS[-1]:
        return False
    return True

def posicao_valida_tupla(pos):
    # retorna uma tupla da posicao_valida
    v, h = pos
    return posicao_valida(v, h)

def come_pedra(tabuleiro, jogador, atual, done=None):
    if done is None:
        done = []
    dn = 1
    dt = 2
    caminhos = []
    v, h = atual
    # se o proximo vizinho é um inimigo e a proxima é uam casa em branco, come a peca, e dai verifica de novo se pode comer através de recursão
    for movimento in ((1, -1), (1, 1), (-1, 1), (-1, -1)):
        prox = map(lambda x: x * dn, movimento)
        prox = v + prox[0], chr(ord(h) + prox[1])
        captura = map(lambda x: x * dt, movimento)
        captura = v + captura[0], chr(ord(h) + captura[1])
        # arrumar...
        if (posicao_valida(*prox) and posicao_valida(*captura)
            and prox not in done
            and tabuleiro[captura] == VAZIO
            and tabuleiro[prox] not in (VAZIO, jogador)):
            done.append(prox)
            n, mvtos, capturada = come_pedra(tabuleiro, jogador, captura, done[:])
            n += 1
            capturada = (prox,) + capturada
            mvtos = (captura,) + mvtos
            caminhos.append((n, mvtos, capturada))
    caminhos.sort(lambda a, b: cmp(a[0], b[0]))
    return caminhos and caminhos[-1] or (0, (), ())

def rainha_come_pedra(tabuleiro, jogador, atual, done=None):
    if done is None:
        done = []
    # busca na diagonal se tem lgum jogador se houver, e a proxima estiver livre come a peça
    d_movimentos = rainha_movimentos_validos(atual)
    caminhos = []
    v, h = atual
    for movimentos in d_movimentos:
        movimentos = map(lambda m, v=v, h=h: (v + m[0], c_proximo(h, m[1])), movimentos)
        movimentos = filter(posicao_valida_tupla, movimentos)
        prox = encontra_inimigo(tabuleiro, jogador, movimentos)
        # não pode ter + de uma peça nadiagonal
        if prox is None or prox in done:
            continue
        movimentos = encontra_fim(tabuleiro, prox, movimentos)
        if not movimentos:
            continue
        done.append(prox)
        # parada da peça
        for captura in movimentos:
            n, mvtos, capturada = rainha_come_pedra(tabuleiro, jogador, captura, done[:])
            n += 1
            capturada = (prox,) + capturada
            mvtos = (captura,) + mvtos
            caminhos.append((n, mvtos, capturada))
    caminhos.sort(lambda a, b: cmp(a[0], b[0]))
    return caminhos and caminhos[-1] or (0, (), ())

def encontra_inimigo(tabuleiro, jogador, movimentos):
    # procura a primeira posição onde tem um inimigo e exibea lista de movimentos
    saidaro = jogador == PRETO and BRANCO or PRETO
    for movimento in movimentos:
        if tabuleiro[movimento] == saidaro:
            return movimento
    return None

def encontra_fim(tabuleiro, atual, movimentos):
    # da a posição final
    inicio = movimentos.index(atual) + 1
    dest = []
    for movimento in movimentos[inicio:]:
        if tabuleiro[movimento] == VAZIO:
            dest.append(movimento)
        else:
            break
    return dest

def movimentos_validos(tabuleiro, to_movimento):
    # exibe os movimentos válidos
    locs = []
    valid = []
    # Encontra as peça do jogador
    for k, v in tabuleiro.items():
        if v == to_movimento:
            locs.append(k)
    d = to_movimento == BRANCO and 1 or -1
    captura = False
    # pra cada posição verifica se pode ir para uam casa em branco
    for v, h in locs:
        atual = (v, h)
        q = rainha(tabuleiro[atual])
        if not q:
            # peças normai anda uam casa na diagonal
            d_movimentos = ([(d, 1)], [(d, -1)])
        else:
            d_movimentos = rainha_movimentos_validos(atual)
        for movimentos in d_movimentos:
            # busca todos os movimentos pela diagonal
            movimentos = map(lambda m, v=v, h=h: (v + m[0], c_proximo(h, m[1])), movimentos)
            # tira movimentos invalidos
            movimentos = filter(posicao_valida_tupla, movimentos)
            for movimento in movimentos:
                if tabuleiro[movimento] == VAZIO:
                    valid.append((atual, movimento, (), ()))
                else:
                    # se achar um valor com alguma peça para
                    break
        if not q:
            n, mvtos, capturada = come_pedra(tabuleiro, to_movimento, atual)
        else:
            # movimento da rainha
            n, mvtos, capturada = rainha_come_pedra(tabuleiro, to_movimento, atual)
        if n:
            captura = True
            valid.append((atual, mvtos[-1], mvtos, capturada))
    if captura:
        valid = filter(lambda movimento: len(movimento[3]) > 0, valid)
    return valid

def rainha_movimentos_validos(atual):
    v, h = atual
    # direçoes
    direcoes = ((1, -1), (1, 1), (-1, 1), (-1, -1))
    # Get the minimum distance to top
    a = min(C_NUMEROS[-1] - v, ord(C_LETRAS[-1]) - ord(h))
    # a distancia minima
    b = min(v - C_NUMEROS[0], ord(h) - ord(C_LETRAS[0]))
    # e a máxima
    r = max(a, b)
    movimentos = []
    # todos os mobimento possiveis
    for d in direcoes:
        m = []
        movimentos.append(m)
        nv, nh = d
        for i in range(1, r + 1):
            m.append((nv * i, nh * i))
    return movimentos

def evoluir(tabuleiro, peca, movimento):
    # testa se a peça c torna rainha com o movimento
    v, h = movimento
    if (not rainha(tabuleiro[movimento]) and
        (v == 8 and peca == BRANCO) or
        (v == 1 and peca == PRETO)):
        return True
    return False

class Damas:

    def __init__(self, to_movimento, saida=saida.Saida()):
        self.saida = saida
        tabuleiro = {}
        inicializa(tabuleiro)
        movimentos = movimentos_validos(tabuleiro, to_movimento)
        self.inicio = utils.Struct(to_movimento=to_movimento, estado_final=0,
                                    tabuleiro=tabuleiro, movimentos=movimentos)

    def faz_movimento(self, movimento, estado):
        if movimento not in estado.movimentos:
            raise ValueError, 'Invalid movimento %r' % movimento
        atual, dest, mvtos, capturada = movimento
        tabuleiro = estado.tabuleiro.copy()
        v, h = dest
        peca = tabuleiro[atual]
        # verifica se a peça chega a "evoluir"
        if evoluir(tabuleiro, peca, dest):
            peca = evolui(peca)
        # Move a peça, setao valor de antes pra vazio
        tabuleiro[dest] = peca
        tabuleiro[atual] = VAZIO
        # se alguma preça foi comida no movimento, seta ela pra vazio tbem
        for captura in capturada:
            tabuleiro[captura] = VAZIO
        # alterna os jogadore
        to_movimento = estado.to_movimento == PRETO and BRANCO or PRETO
        movimentos = movimentos_validos(tabuleiro, to_movimento)
        return utils.Struct(to_movimento=to_movimento,
                            tabuleiro=tabuleiro, movimentos=movimentos)

    def acaba(self, estado):
        # se não tiver + nenhum movimento permitido, acabou
        return not self.movimentos_permitidos(estado)

    def movimentos_permitidos(self, estado):
        # movimentos permitidos da peça
        return estado.movimentos

    def estado_final(self, estado, jogador):
        return estado.estado_final

    def exibe_tabuleiro(self, estado):
        self.saida.modo_texto(self, estado)

    def exibe_movimento(self, movimento, estado):
        # exibe o movimento
        print '%s:' % estado.to_movimento,
        atual, dest, mvtos, capturada = movimento
        if mvtos:
            for mvto in mvtos:
                print '%s%s-%s%s ' % (atual[0], atual[1],
                                      mvto[0], mvto[1]),
                atual = mvto
            print 'X',
        else:
            print '%s%s-%s%s ' % (atual[0], atual[1], dest[0], dest[1]),
        print '\n'
